package com.innovation.ic.sc.gateway.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.Claim;
import com.google.common.base.Strings;
import com.innovation.ic.sc.base.model.sc.Client;
import com.innovation.ic.sc.base.pojo.constant.Constants;
import com.innovation.ic.sc.base.pojo.constant.HttpHeader;
import com.innovation.ic.sc.base.pojo.constant.handler.RedisStorage;
import com.innovation.ic.sc.base.pojo.global.Context;
import com.innovation.ic.sc.base.pojo.variable.ApiResult;
import com.innovation.ic.sc.base.service.ServiceHelper;
import com.innovation.ic.sc.base.value.FilterParamConfig;
import com.innovation.ic.sc.base.value.RunEnvConfig;
import lombok.SneakyThrows;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 验证header中是否有Authorization和Token属性，登录相关的接口不校验token信息
 */
@Component
public class AccessFilter implements GlobalFilter, Ordered {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private RunEnvConfig runEnvConfig;

    @Resource
    private FilterParamConfig config;

    private static FilterParamConfig filterParamConfig;

    @PostConstruct
    public void init() {
        filterParamConfig = config;
    }

    @Resource
    private ServiceHelper serviceHelper;

    /**
     * /**
     * 过滤规则：
     * 1.是否是允许通过的路径。
     * 2.header中是否有clientId，是否合法。
     * 3.header中是否有token，token格式为"Bearer "。如果有，则调用erp的接口判断是否有效。
     *
     * @param exchange exchange
     * @param chain    chain
     * @return 返回结果
     */
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 如果是允许直接通过了路径，则放行
        String path = request.getPath().toString();
        List<String> allowPathList = filterParamConfig.getAllowPathList();
        for (int i = 0; i < allowPathList.size(); i++) {
            if (path.contains(allowPathList.get(i))) {
                logger.info("请求【" + path + "】中包含路径【" + allowPathList.get(i) + "】，直接放行");
                return chain.filter(exchange);
            }
        }

        // 判断是否是登录接口，登录接口直接放行
        List<String> loginPathList = filterParamConfig.getLoginPathList();
        for (int i = 0; i < loginPathList.size(); i++) {
            if (path.contains(loginPathList.get(i))) {
                logger.info("请求【" + path + "】中包含路径【" + loginPathList.get(i) + "】，登录相关接口调用直接放行");
                return chain.filter(exchange);
            }
        }

        // 判断是否是验证码接口，验证码接口直接放行
        List<String> verificationCodePathList = filterParamConfig.getVerificationCodePathList();
        for (int i = 0; i < verificationCodePathList.size(); i++) {
            if (path.contains(verificationCodePathList.get(i))) {
                logger.info("请求【" + path + "】中包含路径【" + verificationCodePathList.get(i) + "】，验证码接口调用直接放行");
                return chain.filter(exchange);
            }
        }

        HttpHeaders httpHeaders = request.getHeaders();
        Set<String> httpHeaderKeySet = httpHeaders.keySet();
        if (null != httpHeaders && !httpHeaders.isEmpty()) {
            // 验证header中的clientId，key为authorization
            Set<String> clientSet = (Set<String>) Context.get(RedisStorage.CLIENT);
            if (!httpHeaderKeySet.contains(filterParamConfig.getAuthorization())) {
                logger.warn("请求的header中没有{}", filterParamConfig.getAuthorization());

                ServerHttpResponse serverHttpResponse = exchange.getResponse();
                serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                ApiResult<String> apiResult = new ApiResult<>();
                apiResult.setSuccess(Boolean.FALSE);
                apiResult.setCode(ApiResult.AUTHORIZATION_NOT_IN_HEADER);
                String apiResultString = JSON.toJSONString(apiResult);
                DataBuffer dataBuffer = serverHttpResponse.bufferFactory().allocateBuffer().write(apiResultString.getBytes(StandardCharsets.UTF_8));
                return serverHttpResponse.writeWith(Mono.just(dataBuffer));
            } else {
                String clientId = httpHeaders.get(filterParamConfig.getAuthorization()).get(0);
                Iterator clientIterator = clientSet.iterator();
                Client client;
                boolean clientIdExist = false;
                while (clientIterator.hasNext()) {
                    client = JSON.parseObject((String) clientIterator.next(), Client.class);
                    if (clientId.equals(client.getId())) {
                        clientIdExist = true;
                        break;
                    }
                }
                if (!clientIdExist) {
                    logger.warn("header中的clientId参数错误");

                    ServerHttpResponse serverHttpResponse = exchange.getResponse();
                    serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                    ApiResult<String> apiResult = new ApiResult<>();
                    apiResult.setSuccess(Boolean.FALSE);
                    apiResult.setCode(ApiResult.WRONG_CLIENT_ID_IN_HEADER);
                    String apiResultString = JSON.toJSONString(apiResult);
                    DataBuffer dataBuffer = serverHttpResponse.bufferFactory().allocateBuffer().write(apiResultString.getBytes(StandardCharsets.UTF_8));
                    return serverHttpResponse.writeWith(Mono.just(dataBuffer));
                } else {
                    logger.info("header中的clientId参数为{}", clientId);
                }
            }

            // 验证header中的token，key是token
            String token;
            if (!httpHeaderKeySet.contains(filterParamConfig.getToken())) {
                logger.warn("请求的header中没有{}", filterParamConfig.getToken());

                ServerHttpResponse serverHttpResponse = exchange.getResponse();
                serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                ApiResult<String> apiResult = new ApiResult<>();
                apiResult.setSuccess(Boolean.FALSE);
                apiResult.setCode(ApiResult.TOKEN_NOT_IN_HEADER);
                String apiResultString = JSON.toJSONString(apiResult);
                DataBuffer dataBuffer = serverHttpResponse.bufferFactory().allocateBuffer().write(apiResultString.getBytes(StandardCharsets.UTF_8));
                return serverHttpResponse.writeWith(Mono.just(dataBuffer));
            } else {
                String tokenString = httpHeaders.get(filterParamConfig.getToken()).get(0);
                token = tokenString.split(HttpHeader.TOKEN_SPLIT)[1];

                // 校验token是否有效
                Boolean result = judgeTokenIfEffective(token);
                if (!result) {
                    logger.warn("header中的token参数不合法");

                    ServerHttpResponse serverHttpResponse = exchange.getResponse();
                    serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                    ApiResult<String> apiResult = new ApiResult<>();
                    apiResult.setSuccess(Boolean.FALSE);
                    apiResult.setCode(ApiResult.WRONG_TOKEN_IN_HEADER);
                    String apiResultString = JSON.toJSONString(apiResult);
                    DataBuffer dataBuffer = serverHttpResponse.bufferFactory().allocateBuffer().write(apiResultString.getBytes(StandardCharsets.UTF_8));
                    return serverHttpResponse.writeWith(Mono.just(dataBuffer));
                } else {
                    logger.info("header中的token校验通过");
                }
            }

            // 验证redis中的token（用户信息）是否存在
            Object tokenObject = serviceHelper.getRedisManager().get(RedisStorage.TOKEN_PREFIX + token);
            if (null == tokenObject) {
                logger.warn("redis中{}不存在，需要重新登录", RedisStorage.TOKEN_PREFIX + token);

                ServerHttpResponse serverHttpResponse = exchange.getResponse();
                serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                ApiResult<String> apiResult = new ApiResult<>();
                apiResult.setSuccess(Boolean.FALSE);
                apiResult.setCode(ApiResult.TONE_NOT_IN_REDIS);
                String apiResultString = JSON.toJSONString(apiResult);
                DataBuffer dataBuffer = serverHttpResponse.bufferFactory().allocateBuffer().write(apiResultString.getBytes(StandardCharsets.UTF_8));
                return serverHttpResponse.writeWith(Mono.just(dataBuffer));
            } else {
                logger.info("redis中{}存在", RedisStorage.TOKEN_PREFIX + token);
            }
        } else {
            logger.warn("请求的header是空");

            ServerHttpResponse serverHttpResponse = exchange.getResponse();
            serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            ApiResult<String> apiResult = new ApiResult<>();
            apiResult.setSuccess(Boolean.FALSE);
            apiResult.setCode(ApiResult.EMPTY_HEADER);
            String apiResultString = JSON.toJSONString(apiResult);
            DataBuffer dataBuffer = serverHttpResponse.bufferFactory().allocateBuffer().write(apiResultString.getBytes(StandardCharsets.UTF_8));
            return serverHttpResponse.writeWith(Mono.just(dataBuffer));
        }

        return chain.filter(exchange);
    }

    /**
     * 校验token是否有效
     *
     * @param token token信息
     * @return 返回校验结果
     */
    private Boolean judgeTokenIfEffective(String token) {
        Boolean result = Boolean.FALSE;
        if (!Strings.isNullOrEmpty(token)) {
            try {
                // 解密token
                Map<String, Claim> claims = JWT.decode(token).getClaims();
                if (!claims.isEmpty()) {
                    logger.info("当前请求token有效,校验成功");
                    result = Boolean.TRUE;
                } else {
                    logger.info("token解密失败,校验token失败");
                }
            } catch (Exception e) {
                logger.error("token校验失败,原因:", e);
            }
        } else {
            logger.info("当前请求token为空");
        }

        if (!result && !Constants.PROD_RUN_ENVIRONMENT.equals(runEnvConfig.getEnv())) {
            logger.info("当前环境参数为:[{}],token无效时不拦截请求", runEnvConfig.getEnv());
            result = true;
        }

        return result;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}